package com.lb.spring.framework.context;

import com.lb.spring.framework.annotation.LbAutowired;
import com.lb.spring.framework.annotation.LbController;
import com.lb.spring.framework.annotation.LbService;
import com.lb.spring.framework.aop.LbAopProxy;
import com.lb.spring.framework.aop.LbCglibAopProxy;
import com.lb.spring.framework.aop.LbJdkDynamicAopProxy;
import com.lb.spring.framework.aop.config.LbAopConfig;
import com.lb.spring.framework.aop.support.LbAdvisedSupport;
import com.lb.spring.framework.beans.LbBeanFactory;
import com.lb.spring.framework.beans.LbBeanWrapper;
import com.lb.spring.framework.beans.config.LbBeanDefinition;
import com.lb.spring.framework.beans.config.LbBeanPostProcessor;
import com.lb.spring.framework.beans.support.LbBeanDefinitionReader;
import com.lb.spring.framework.beans.support.LbDefaultListableBeanFactory;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Title: LbApplicationContext
 * @Description: TODO
 * @Author lb
 * @Date 2019/04/12  17:37
 */
public class LbApplicationContext extends LbDefaultListableBeanFactory implements LbBeanFactory {

    private String[] configLocations;
    private LbBeanDefinitionReader reader;
    //用来保证注册式单例的容器
    private Map<String,Object> factoryBeanObjectCache = new HashMap<String, Object>();
    //用来存储所有的被代理过的对象
    private Map<String, LbBeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<String,LbBeanWrapper>();
    public LbApplicationContext(String... configLocations){
        this.configLocations=configLocations;
        try {
            refresh();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Override
    protected void refresh() throws Exception{

        /**
         * 1.LbBeanDefinitionReader的构造方法，负责把properties中配置信息扫描出来，然后得到所有的类的名字,放在一个名为registyBeanClasses的List中
         * 2.loadBeanDefinitions()方法负责用反射的方法“Class.forName(className)”来完成把每一个配置信息解析成一个BeanDefinition
         */
        //1、定位配置文件、其中包括annotation、xml、properties等类型的配置文件
        reader = new LbBeanDefinitionReader(this.configLocations);
        //2、加载配置文件，扫描相关的类，把他们封装成BeanDefinition
        List<LbBeanDefinition> beanDefinitions = reader.loadBeanDefinitions();
        //3、注册，把配置信息放到容器里面（伪IoC容器）
        doRegisterBeanDefiniton(beanDefinitions);
        //4、把不是延时加载的类，提前初始化
        doAutowired();
    }

    //只处理非延时加载的情况
    private void doAutowired() {
        for (Map.Entry<String, LbBeanDefinition> beanDefinitionEntry : super.beanDefinitionMap.entrySet()) {
            String beanName = beanDefinitionEntry.getKey();
            if(!beanDefinitionEntry.getValue().isLazyInit()) {
                try {
                    getBean(beanName);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void doRegisterBeanDefiniton(List<LbBeanDefinition> beanDefinitions) throws Exception {
        for (LbBeanDefinition beanDefinition: beanDefinitions) {
            if(super.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())){
                throw new Exception("The “" + beanDefinition.getFactoryBeanName() + "” is exists!!");
            }
            //beanDefinition.getFactoryBeanName()为首字母小写的类名，区别于beanDefinition.getBeanClassName()的"com.xx.xx.xx"
            super.beanDefinitionMap.put(beanDefinition.getFactoryBeanName(),beanDefinition);
        }
        //到这里为止，容器初始化完毕

    }

    /**
     * 依赖注入，从这里开始，通过读取BeanDefinition中的信息
     * 然后，通过反射机制创建一个实例并返回
     * Spring做法是，不会把最原始的对象放出去，而会用一个BeanWrapper来进行一次包装
     * 装饰器模式：
     *  1、保留原来的OOP关系
     *  2、我需要对它进行扩展，增强（为了以后AOP打基础）
     * @param beanName
     * @return
     */
    @Override
    public Object getBean(String beanName) throws Exception {

        LbBeanDefinition lbBeanDefinition = this.beanDefinitionMap.get(beanName);
        Object instance = null;

        //这个逻辑还不严谨，自己可以去参考Spring源码
        //工厂模式 + 策略模式
        LbBeanPostProcessor postProcessor = new LbBeanPostProcessor();

        postProcessor.postProcessBeforeInitialization(instance,beanName);

        instance = instantiateBean(beanName,lbBeanDefinition);

        //3、把这个对象实例封装到BeanWrapper中
        LbBeanWrapper beanWrapper = new LbBeanWrapper(instance);

        //singletonObjects

        //factoryBeanInstanceCache

        //4、把BeanWrapper存到IOC容器里面
//        //1、初始化

//        //class A{ B b;}
//        //class B{ A a;}
//        //先有鸡还是先有蛋的问题，一个方法是搞不定的，要分两次

        //2、拿到BeanWraoper之后，把BeanWrapper保存到IOC容器中去
        this.factoryBeanInstanceCache.put(beanName,beanWrapper);

        postProcessor.postProcessAfterInitialization(instance,beanName);

//        //3、注入
        populateBean(beanName,new LbBeanDefinition(),beanWrapper);


        //这里拿到BeanWrapper中的private Object wrappedInstance，然后就可以调用被@autowired注解的类属性的方法了
        return this.factoryBeanInstanceCache.get(beanName).getWrappedInstance();
    }

    private Object instantiateBean(String beanName, LbBeanDefinition lbBeanDefinition) {
        //1、拿到要实例化的对象的类名,这里是com.xxx.xxx.xxx格式的
        String className = lbBeanDefinition.getBeanClassName();

        //2、反射实例化，得到一个对象
        Object instance = null;
        try {
            // lbBeanDefinition.getFactoryBeanName()
            //假设默认就是单例,细节暂且不考虑，先把主线拉通
            if(this.factoryBeanObjectCache.containsKey(className)){
                instance = this.factoryBeanObjectCache.get(className);
            }else {
                Class<?> clazz = Class.forName(className);
                instance = clazz.newInstance();
                LbAdvisedSupport config = instantionAopConfig(lbBeanDefinition);
                config.setTargetClass(clazz);
                config.setTarget(instance);
                //符合PointCut的规则的话，闯将代理对象
                if(config.pointCutMatch()) {
                    instance = createProxy(config).getProxy();
                }
//                this.singletonBeanCacheMap.put(className,instance);
                this.factoryBeanObjectCache.put(lbBeanDefinition.getBeanClassName(),instance);
            }
        }catch (Exception e){
            e.printStackTrace();
        }

        return instance;
    }

    private LbAdvisedSupport instantionAopConfig(LbBeanDefinition beanDefinition) throws Exception{
        LbAopConfig config = new LbAopConfig();
        config.setPointCut(reader.getConfig().getProperty("pointCut"));
        config.setAspectClass(reader.getConfig().getProperty("aspectClass"));
        config.setAspectBefore(reader.getConfig().getProperty("aspectBefore"));
        config.setAspectAfter(reader.getConfig().getProperty("aspectAfter"));
        config.setAspectAfterThrow(reader.getConfig().getProperty("aspectAfterThrow"));
        config.setAspectAfterThrowingName(reader.getConfig().getProperty("aspectAfterThrowingName"));
        return new LbAdvisedSupport(config);
    }
    private LbAopProxy createProxy(LbAdvisedSupport config) {
        Class targetClass = config.getTargetClass();
        if (targetClass.getInterfaces().length > 0) {
            return new LbJdkDynamicAopProxy(config);
        }
        return new LbCglibAopProxy(config);
    }

    private void populateBean(String beanName, LbBeanDefinition lbBeanDefinition, LbBeanWrapper lbBeanWrapper) {
        Object instance = lbBeanWrapper.getWrappedInstance();

//        gpBeanDefinition.getBeanClassName();

        //这里返回wrappedInstance.getClass()
        Class<?> clazz = lbBeanWrapper.getWrappedClass();
        //判断只有加了注解的类，才执行依赖注入
        if(!(clazz.isAnnotationPresent(LbController.class) || clazz.isAnnotationPresent(LbService.class))){
            return;
        }

        //获得所有的fields
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            if(!field.isAnnotationPresent(LbAutowired.class)){ continue;}

            LbAutowired autowired = field.getAnnotation(LbAutowired.class);

            String autowiredBeanName =  autowired.value().trim();
            if("".equals(autowiredBeanName)){
                autowiredBeanName = field.getType().getName();
            }

            //强制访问
            field.setAccessible(true);

            try {
                //为什么会为NULL，先留个坑
                if(this.factoryBeanInstanceCache.get(autowiredBeanName) == null){ continue; }
//                if(instance == null){
//                    continue;
//                }
                //这里把field设置成了BeanWrapper中的private Object wrappedInstance;
                field.set(instance,this.factoryBeanInstanceCache.get(autowiredBeanName).getWrappedInstance());
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }

        }
    }
    public String[] getBeanDefinitionNames() {
        return this.beanDefinitionMap.keySet().toArray(new  String[this.beanDefinitionMap.size()]);
    }

    public int getBeanDefinitionCount(){
        return this.beanDefinitionMap.size();
    }

    public Properties getConfig(){
        return this.reader.getConfig();
    }
}
